#include "passwordhelper.h"

#include <botan/s2k.h>
#include <botan/exceptn.h>

using namespace Botan;


PasswordHelper::PasswordHelper(QString salt) :
    mInit("thread_safe=true"), mInitialized(false)
{
    init(salt, "MakeABetterPassphrase");
    // TODO Instead of the hardcoded passphrase, implement better way to get
    // it by querying it from user beforehand or hashing it from somewhere
    // where user interaction would not be needed.
}

PasswordHelper::~PasswordHelper()
{
}


void PasswordHelper::init(QString salt, QString passphrase)
{
    try {
        S2K* s2k(get_s2k("PBKDF2(SHA-256)"));
        s2k->set_iterations(4096);

        // Use the salt, if one was given
        bool gotSalt(false);

        if (!salt.isEmpty()) {
            try {
                OctetString theSalt(salt.toAscii().data());
                s2k->change_salt(theSalt.bits_of());
                gotSalt = true;
            }
            // FIXME For some reason the Botan::Exceptions do not seem to get caught here. Investigate why.
            //catch (Botan::Exception& e)
            catch (std::exception e) {
                //qDebug("PasswordHelper: Given salt was invalid");
                // New salt will be generated.
            }
        }

        if (!gotSalt) {
            // Generate a new salt
            AutoSeeded_RNG rng;
            s2k->new_random_salt(rng, 8); // 8 octets, 64-bit salt
        }

        // Derive the key from the salt and passphrase
        SymmetricKey key = s2k->derive_key(16, passphrase.toAscii().data());
        mKey = key.as_string();

        // ready to roll
        mInitialized = true;

        // Save the salt
        OctetString currentSalt = s2k->current_salt();
        mSalt = currentSalt.as_string().c_str();
    } catch (Botan::Exception &) {
        //qDebug("PasswordHelper::init error: ");
    }
}

QString PasswordHelper::salt()
{
    return mSalt;
}

bool PasswordHelper::decrypt(QString &in, QString *out)
{
    if (!mInitialized || out == NULL)
        return false;

    bool success(false);


    try {
        // Create the decoder
        SymmetricKey key(mKey);
        std::string alg = "Serpent/CBC/PKCS7";
        Pipe decPipe(new Hex_Decoder, get_cipher(alg, key, DECRYPTION));

        // take the encrypted password and decode it
        std::string inputEncStr = in.toAscii().data();
        decPipe.process_msg(inputEncStr);
        std::string decryptedStr = decPipe.read_all_as_string();

        // set the decrypted string
        *out = decryptedStr.c_str();

        // decryption was successful
        success = true;
    }
    //catch (Botan::Decoding_Error de)
    // FIXME For some reason the Botan::Exceptions do not seem to get caught here. Investigate why.
    // Meanwhile, catch std:exceptions and assume it's decoding error
    catch (std::exception) {
        // If there was an error decoding the string
        //qDebug("PasswordHelper: Error decrypting.");
    }

    return success;
}

bool PasswordHelper::encrypt(QString &in, QString *out)
{
    if (!mInitialized || out == NULL)
        return false;

    bool success(false);

    try {
        // Create the encoder
        SymmetricKey key(mKey);
        std::string alg = "Serpent/CBC/PKCS7";
        Pipe encPipe(get_cipher(alg, key, ENCRYPTION), new Hex_Encoder);

        // Get the plain password string
        std::string inputStr = in.toAscii().data();

        // Encrypt it and set the result
        encPipe.process_msg(inputStr);
        std::string encryptedStr = encPipe.read_all_as_string();
        *out = encryptedStr.c_str();

        success = true;
    }
    // FIXME For some reason the Botan::Exceptions do not seem to get caught here. Investigate why.
    catch (std::exception) {
        //qDebug("PasswordHelper: Error encrypting.");
    }

    return success;
}
